查看原文
其他

连连看逆向

自己的小白 看雪学院 2021-03-07

本文为看雪论坛精华文章

看雪论坛作者ID:自己的小白



需要实现的功能



1、找到原程序的exe

2、去掉程序中的广告

3、写一个连连看的辅助



使用工具



VS2017、OD、CE、EXEINFOPE、Spy++、010Editor、PCHunter(可用任务管理器替代)



分析环境



Win7虚拟机


分析思路



1、找到原程序exe

(1)原程序被加壳,需要脱壳。

(2)采用多进程,使用进程遍历工具分析,调试,CreateProcessA/W下断分析。

2、去广告

(1)广告弹出方式:对话框或者网页。

(2)在一些API上下断点(下面是些常用的,当然还有,这里就不列举了),通过栈回溯分析。
  • DialogBoxA/W 对话框弹出API

  • CreateWindowExA/W 创建窗口API

  • WinExec 启动一个可执行文件API

  • CreateProcessA/W 创建进程

  • ShellExecuteA/W 创建进程


3、写一个连连看的辅助


功能:一键秒杀(这里我们采用第二种,利用道具)


(1)分析+算法+模拟点击

  • 找到连连看数组,使用算法完成自动连接

  • 模拟点击对应位置,完成一键秒杀


(2)利用游戏本身功能函数完成一键秒杀

  • 分析道具,使用指南针完成自动连接

  • 找到消除CALL,实现一键秒杀


4、需要分析以下数据以及代码:

(1)连连看数组

(2)指南针CALL

(3)消除CALL



需要使用的技术



1、MFC DLL

使用MFC DLL,方便之处在于不需要自己写DLLMain的case了,直接写在InitInstance函数中即可,且调试时使用Cstring比较方便。

2、SetWindowLong

修改窗口回调函数,在自己的窗口回调函数中处理快捷键响应。

3、CallWindowProc

调用指定窗口回调函数。

4、多线程

_beginthreadex创建线程便于弹窗。


开始破解



1、我们先运行程序,会弹出下面的对话框:
 
 
2、用PCHunter查看运行中的进程,此时只有一个进程,就是我们打开的exe:
 
 
3、点击开始游戏,又会弹出下面的对话框,再用PCHunter查看运行中的进程,此时多了一个新的进程qqllk.ocx。
 
 
 
4、先不管它,我们点击继续,终于来到我们游戏主体,再用PCHunter查看,发现qqllk.ocx退出,多出一个kyodai.exe。
 

 
5、由于最终运行的是kyodai.exe,我们可以假设kyodai.exe就是我们要找的游戏原进程,我们回去双击kyodai.exe,发现点击它又运行不了。但是通过其它程序又能运行它,由此猜想其它程序在打开kyodai.exe之时先对kyodai.exe的内存进行了修改,然后使其能正常运行。那是哪个程序修改的它呢,前面分析有一个进程qqllk.ocx在kyodai.exe运行后就退出了,我们大胆假设就是这个打开的我们游戏主体进程,并对其进行修改。
 
下面用OD对其进行分析:
 
 
6、重新打开游戏,到这一步,通过上面我们知道,此时我们产生了一个新进程qqllk.ocx,现在我们用OD附加这个进程,猜想它可能使用了CreateProcessA/W函数打开进程,由于不知道到底是A版还是W版,我们在两个函数上面都设置上断点。
 
 
7、运行之后发现在CreateProcessA函数上断下,并且kyodai.exe程序被它以挂起的方式打开了,很多恶意程序都是这样的套路,那么就容易猜想了,挂起了程序之后就可能修改内存数据,之后恢复程序,那么我们就在修改内存数据函数WriteProcessMemory下断点,运行。
 
 
8、发现果然断在了我们下的WriteProcessMemory函数上,并且观察堆栈,会发现该函数是在kyodai.exe程序中的0x43817A地址处修改了1个字节的数据。数据窗口跟随可以看出是修改后的数据是0。
 
 
9、然后在唤醒线程函数ResumeThread下断点,运行之后果然走到了这里。
 
 
10、那么我们现在就已经分析处理其运行机制,它首先运行qqllk.ocx,然后再由qqllk.ocx执行CreateProcessA以挂起的方式打开kyodai.exe,再执行WriteProcessMemory修改kyodai.exe内的0x43817A地址处的一个字节,将其改为0,再执行ResumeThread恢复kyodai.exe进程。
 
11、那么我们现在可以直接使用010Editor打开kyodai.exe直接找到3817A(因为默认加载基址0x400000,需要减去它得到文件偏移)地址,把里面的数据改为00,如果文件是只读的情况,改不了,可以将文件复制一份,修改复制后的文件,保存,运行复制后的文件。
 
 
12、运行我们修改后的kyodai.exe,发现直接就来到了游戏主体,到此,我们找到了游戏的原程序并且去掉了广告。接下来就是分析游戏关键功能了。
 
 
13、点击练习的时候地图会随机刷新,那么肯定会用到rand这个随机函数,我们用OD附加我们原程序,再在rand函数下断点,点击游戏中的练习按钮,就会断在我们rand函数处,然后在点击OD工具栏上的K进行栈回溯分析。
 
 
14、观察发现有2个上层调用(带程序名字的2个),它们的关系是0x41A085处的函数调用0x41CAF2的函数,0x41CAF2再调用rand函数。在K中越靠下就越是外层的函数。
 
 
15、双击分别进入上面两个地址,并在它们上面设置断点,然后把之前rand的断点删除。
 
 
 
16、F9运行后再次在游戏中点击练习会在第一个断点0x41A085处断下,这是一个函数CALL,要重点关注一下它上面一行代码MOV ECA、EDI、由于这是C++所写的程序,它都会使用ECX这个寄存器传递this指针也就是传递一个对象。

之后进入函数要留意一下ECX寄存器。按F7进入这个函数,先Ctrl+A分析一下该模块,发现一开始就把ECX的值传给了ESI,先不管继续单步,没几步就看见了字符串start.wav,结合下面的 %sSound\%s分析这可能是个音乐文件,我们在音乐文件夹中去找找看,发现真的是,这里就差不多可以证明这个函数就是初始化游戏的哈桑函数。
 
 
 
17、然后再快速单步很快就能发现刚才所下的第二个断点,发现rand下面有一个memcpy拷贝内存的函数,走一遍注意观察OD中右上角寄存器的变化,我们刚刚看到这个函数里面,是将ECX传递给了ESI,而0x41CAFC地址处对ESI进行了访问,走一步之后我们发现EAX的值是0x12BB50,那么选中它,右键->数据窗口跟随 ,查看0x13BB50地址里面有些什么数据。
 
 
 
18、然后单步走到memcpy下一步处,观察0x12BB50地址里的数据,多来几次,发现除了前8个字节没变,后面都被填充成某种规律的010100...猜测0x12BB50可能是游戏的地图数组基地址,先不管,继续单步,当走到0x41CB15时,0x12BB50处数据又被刷新了。所以0x12BB50极有可能是我们要找的地图数组基地址。

 
 
19、为了验证是否正确,按F9让游戏运行起来,观察地图与该内存数据是否有联系,多来几次,发现地图和OD中的内存非常相似。

 
20、为了再次确认,我们把游戏中的物品点击消除掉在观察内存发现被清0了,那么我们就可以确定0x12BB50就是地图数组基地址了,并且得到了0x12BB58就是地图数据的起始位置了。

 
 
21、现在,我们来捋一捋思路,先将地图数组变成有规律的0与1,然后再将1上的位置随机分配各种代表图案的数值,而前一步并没有循环随机函数,只随机了一次,大胆假设它应该存在一个保存各个地图基本形状的数组,随机函数是随机使用那个地图,经过分析我在rand函数上面最近的一个CALL证实了我这个假设。
 
 
22、localkyodaimap.map就保存着我们所有地图的基本形状(有图案还是没有图案),至于是什么图案,是在0x41CB15地址处的CALL里面随机分配的,这里就不细说了。
 
 
 
 
23、接下来就是分析道具了,先从指南针开始。指南针道具是帮助玩家找到可以消除的2个相同图案,而要实现其功能必须要访问地图数组,所以我们可以在0x12BB58地址处下一个内存访问断点。
 
 
24、先在OD中按ALT+B来到断点模块,把其它断点禁止或者删掉,然后运行游戏,点击指南针,程序就会停下来,点击K进行栈回溯分析。发现有5个上层调用,在5个位置处都设置上断点,把刚刚设置的内存访问断点删除掉。
 
 
25、再按F9运行OD发现运行多次都停在0x4292A5处,那么这肯定不是我们要找的函数,去掉其断点。再运行,发现只要点击游戏界面,就会在0x40CACA处断下,所以这个也不是我们要找的,去掉其断点,继续运行,同样的0x41AF11只要点击游戏界面,就会在该地址处断下,pass掉,再按F9运行起来。
 
剩下的0x41E76C与0x41DE5C处的函数可能是我们要找的关键函数,现在想要写代码完成指南针的功能,只需要找到这两处地址的函数调用时所需要的参数,那就可以模拟出指南针的功能。
 
重新运行起来,点击指南针,会在0x41DE5C处断下,选中这一行按Enter键进入这个函数一直往下拉找到末尾RETN观察发现RETN 0xC也就是说0x41DE5C处的函数调用需要3个参数(当然这里不考虑通过ECX寄存器传递的this指针),通过观察堆栈得出参数是0,0,F0。
 
 
 
26、为了确认这3个参数是不是可变的,多操作几次,发现参数没有改变,那现在就只要确认ECX的值,就可以用这个函数模拟指南针功能了。暂时先不管它,那么我们再次运行,使其段在0x41E76C处,按照上面分析方法,这个函数有2个参数,是两个地址,数据窗口跟随,然后单步,看看两处地址的数据有啥变化。
 
 
27、指南针的作用是找出两个可以相消除的图案,也就是要找到这两个图案的坐标,通过观察发现这个CALL有可能是获取两个可以相消除的图案的坐标。然后我们多次将内存中的数值与找到的两个图案相比较,证实了此猜想成立。
 
 
 
28、所以现在只剩下我们刚刚找到的处于0x41DE5C的CALL,只要找到ECX的值就可以了。往上找ECX的值,需要往上层函数分析,这里我们换一种方法分析,使用CE附加程序,搜索ESI的值,发现有几个绿色的基址。
 
 
 
29、我们把这几个基址在OD反汇编窗口右键->查找->所有常量,排除后还剩下0x45DEBC与0x7793CDD8,这里先用0x45DEBC试试。
 
 
 
30、相同的原理我们找出其它道具的调用CALL,这里就不细说了。我们发现,都会调用0x41DE5C处的CALL,只是参数不同,对比下面参数:
 
调用指南针:0,0,F0
 
调用炸弹:0,0,F4
 
调用重列:0,0,F1
 
得出结论第3个参数是道具类型,而0x41DE5C处的函数功能应该是使用道具。
 
 
 
31、找消除CALL,要消除,肯定要对地图数组改写,所以我们可以在相应位置设置内存写入断点,然后点击这两个位置消除,OD就会断下,取消内存写入断点,然后通过栈回溯分析。
 
 
 
32、通过工具栏上的K跳转到栈回溯窗口,发现有5个,按照之前的套路进行分析。最后还有3个CALL有可能是我们要找的,通过分析,发现第一个CALL有7个参数,有点多,而且其中有参数的值不确定,pass,第三个CALL没有传坐标,应该不是,也pass,那么我们就只剩下第2个CALL了,它有6个参数,经过分析,第一个参数为0,第二个参数为地图数组基址(这个前面已经找到),第3,4个参数为两个坐标,我们可以通过上面指南针中找到的获取两个可以消除的点的函数获得。
 
 
 
 
 
33、往上找到(这里需要往上一层找,第5,6参数是上层函数的参数),第5,6个参数的值是在这赋值的,ECX是ESI+0x1E84的值,而ESI和上面我们找到的ESI一样,这就不说了。
 
 



编写辅助工具



下面就是编写辅助了,单消只需要调用获取两个能相消的坐标的函数,再调用消除CALL就可以了,当然使用炸弹道具也可以完成,秒杀就是循环使用单消就行,这里有点,就是如果没有能够相消除的两个点的话,是会返回两个(0,0)坐标,所以,可以根据此退出循环。
 
1、用VS2017首先创建一个MFC的DLL程序,选择在静态库中使用MFC,这样兼容性更好,这个选项也可以在属性->常规->MFC的使用里面更改。
 
 
 
 
2、通过Spy++获得游戏的窗口名与类名,用于FindWindow函数获取游戏窗口句柄。
 
 
3、使用__asm内联汇编调用我们找到的关键函数,说明下,消息采用自定义消息。

LRESULT CALLBACK WindowProc(
    _In_ HWND hWnd,
    _In_ UINT Msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam)
{
    if (Msg == WM_DATA1)
    {
        //指南针
        OutputDebugString(L"指南针");
        __asm {
            mov ecx,0x45DEBC
            mov ecx,[ecx]
            lea ecx,dword ptr ds:[ecx+0x494] //获取this指针
            push 0xF0                                //道具类型,指南针
            push 0                                    //参数2
            push 0                                    //参数1
            mov eax,0x41E691
            call eax //调用道具函数
        }
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    else if (Msg == WM_DATA2)
    {
        //炸弹
        OutputDebugString(L"炸弹");
        __asm {
            mov ecx, 0x45DEBC
            mov ecx, [ecx]
            lea ecx, dword ptr ds : [ecx + 0x494] //获取this指针
            push 0xF4        //道具类型,炸弹
            push 0            //参数2
            push 0            //参数1
            mov eax, 0x41E691
            call eax //调用道具函数
        }
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    else if (Msg == WM_DATA3)
    {
        //重列
        OutputDebugString(L"重列");
        __asm {
            mov ecx, 0x45DEBC
            mov ecx, [ecx]
            lea ecx, dword ptr ds : [ecx + 0x494]
            push 0xF1
            push 0
            push 0
            mov eax, 0x41E691
            call eax
        }
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    else if (Msg == WM_DATA4)
    {
        //秒杀
        OutputDebugString(L"单消");
 
        //1.获取可以连接的两个点
        POINT pt1 = { 0 };
        POINT pt2 = { 0 };
        __asm {
            mov ecx,0x45DEBC
            mov ecx,[ecx]
            lea ecx, dword ptr ds : [ecx + 0x494]
            mov ecx, dword ptr ds : [ecx+0x19F0]
            lea eax,pt1.x
            push eax
            lea eax,pt2.x
            push eax
            mov eax,0x42923F
            call eax
        }
 
        if (pt1.x == 0 && pt1.x == pt1.y)
        {
            return -1;
        }
 
        //2.调用消除CALL
        __asm {
            mov ecx, 0x45DEBC
            mov ecx, [ecx]
            mov eax, dword ptr ds : [ecx + 0x1E84]
            mov eax, dword ptr ds : [eax + 0x50]
            push eax
 
            mov eax, dword ptr ds : [ecx + 0x1E84]
            lea eax, dword ptr ds : [eax + 0x30]
            push eax
 
            lea eax, pt1.x
            push eax
            lea eax,pt2.x
            push eax
 
            lea eax, dword ptr ds : [ecx + 0x494]
            mov eax, dword ptr ds : [eax + 0x19F0]
            mov eax,dword ptr ds:[eax+0x4]
            push eax
            push 0
 
            mov eax,0x41C68E
            call eax
        }
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    return CallWindowProc(g_oldProc, hWnd, Msg, wParam, lParam);
}


4、最后上图:
 
 
游戏链接:
https://pan.baidu.com/s/1kP4-mthdA8-0LIa7Oo0CVw

提取码:h2ea



- End -






看雪ID:自己的小白

https://bbs.pediy.com/user-850153.htm 


*本文由看雪论坛  自己的小白  原创,转载请注明来自看雪社区




推荐文章++++

Windows内存堆内容整理总结

格式化字符串漏洞

安卓源码+内核修改编译(修改内核调试标志绕过反调试)

GlobeImposter3.0 勒索分析

恶意样本检测——Mathematics Malware Detected Tools






进阶安全圈,不得不读的一本书






公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com




“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存